import { chain } from "../../common/utils/async";
import { Disposables } from "../../common/utils/resourceLifecycle";
import { EQEnvInfo } from "./info";
import { EQEnvUpdatedEvent, EQLocatorQuery, IEQEnvsIterator, ILocator } from "./locator";
import { EQEnvsWatchers } from "./watchers";

/**
 * A wrapper around a set of locators, exposing them as a single locator.
 *
 * Events and iterator results are combined.
 */
export class Locators<I = EQEnvInfo> extends EQEnvsWatchers implements ILocator<I> {
    constructor(
        // The locators will be watched as well as iterated.
        private readonly locators: ReadonlyArray<ILocator<I>>,
    ) {
        super(locators);
    }

    public iterEnvs(query?: EQLocatorQuery): IEQEnvsIterator<I> {
        const iterators = this.locators.map((loc) => loc.iterEnvs(query));
        return combineIterators(iterators);
    }
}

export function combineIterators<I>(iterators: IEQEnvsIterator<I>[]): IEQEnvsIterator<I> {
    const result: IEQEnvsIterator<I> = chain(iterators);
    const events = iterators.map((it) => it.onUpdated).filter((v) => v);
    if (!events || events.length === 0) {
        // There are no sub-events, so we leave `onUpdated` undefined.
        return result;
    }

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    result.onUpdated = (handleEvent: (e: EQEnvUpdatedEvent<I> | null) => any) => {
        const disposables = new Disposables();
        let numActive = events.length;
        events.forEach((event) => {
            const disposable = event!((e: EQEnvUpdatedEvent<I> | null) => {
                // NOSONAR
                if (e === null) {
                    numActive -= 1;
                    if (numActive === 0) {
                        // All the sub-events are done so we're done.
                        handleEvent(null);
                    }
                } else {
                    handleEvent(e);
                }
            });
            disposables.push(disposable);
        });
        return disposables;
    };
    return result;
}
